select 函数是网络通信编程中非常常用的一个函数,我们应该熟练掌握它。虽然它是 BSD 标准之一的 Socket 函数之一,但在 Linux 和 Windows 平台,其行为表现还是有点区别的。我们先来看一下 Linux 平台上的 select 函数。
# 4.5.1 Linux 平台下的 select 函数select 函数的作用是检测一组 socket 中某个或某几个是否有“事件”就绪,这里的“事件”一般分为如下三类:
读事件就绪:
socket 内核中,接收缓冲区中的字节数大于等于低水位标记 SO_RCVLOWAT,此时调用 recv 或 read 函数可以无阻塞的读该文件描述符, 并且返回值大于 0; TCP 连接的对端关闭连接,此时调用 recv 或 read 函数对该 socket 读返回 0 值 ; 侦听 socket 上有新的连接请求; socket 上有未处理的错误。写事件就绪:
socket 内核中,发送缓冲区中的可用字节数(发送缓冲区的空闲位置大⼩) 大于等于低水位标记 SO_SNDLOWAT,此时可以无阻塞的写, 并且返回值大于 0; socket 的写操作被关闭(调用了 close 或 shutdown 函数)( 对一个写操作被关闭的 socket 进行写操作, 会触发 SIGPIPE 信号); socket 使⽤非阻塞 connect 连接成功或失败时。异常事件就绪
socket 上收到带外数据。
函数签名如下:
int select(int nfds, fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout); 12345参数说明:
参数 nfds, Linux 下 socket 也称 fd,这个参数的值设置成所有需要使用 select 函数检测事件的 fd 中的最大 fd 值加 1。
参数 readfds,需要监听可读事件的 fd 集合。
参数 writefds,需要监听可写事件的 fd 集合。
参数 exceptfds,需要监听异常事件 fd 集合。
readfds、writefds 和 exceptfds 类型都是 fd_set,这是一个结构体信息,其定义位于 /usr/include/sys/select.h 中:
/* The fd_set member is required to be an array of longs. */typedef long int __fd_mask;/* Some versions of define this macros. */#undef __NFDBITS/* It's easier to assume 8-bit bytes than to get CHAR_BIT. */#define __NFDBITS(8 * (int) sizeof (__fd_mask))#define __FD_ELT(d) ((d) / __NFDBITS)#define __FD_MASK(d)((__fd_mask) 1 fds_bits)#else// 在我的centOS 7.0 系统中的值:// __FD_SETSIZE = 1024//__NFDBITS = 64__fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS]; # define __FDS_BITS(set) ((set)->__fds_bits)#endif } fd_set;/* Maximum number of file descriptors in `fd_set'. */#define FD_SETSIZE __FD_SETSIZE 1234567891011121314151617181920212223242526272829我们假设未定义宏 __USE_XOPEN,将上面的代码整理一下:
typedef struct{ long int __fds_bits[16]; } fd_set; 1234将一个 fd 添加到 fd_set 这个集合中需要使用 FD_SET 宏,其定义如下:
void FD_SET(int fd, fd_set *set); 1其实现如下:
#define FD_SET(fd,fdsetp) __FD_SET(fd,fdsetp) 1FD_SET 在内部又是通过宏 __FD_SET 来实现的,__FD_SET 的定义如下(位于 /usr/include/bits/select.h 中):
#if defined __GNUC__ && __GNUC__ >= 2# if __WORDSIZE == 64# define __FD_ZERO_STOS "stosq"# else# define __FD_ZERO_STOS "stosl"# endif# define __FD_ZERO(fdsp) \ do {\int __d0, __d1;\__asm__ __volatile__ ("cld; rep; " __FD_ZERO_STOS \ : "=c" (__d0), "=D" (__d1) \ : "a" (0), "0" (sizeof (fd_set) \ / sizeof (__fd_mask)), \"1" (&__FDS_BITS (fdsp)[0])\ : "memory");\ } while (0)#else/* ! GNU CC *//* We don't use `memset' because this would require a prototype andthe array isn't too big. */# define __FD_ZERO(set) \ do {\unsigned int __i; \fd_set *__arr = (set);\for (__i = 0; __i < sizeof (fd_set) / sizeof (__fd_mask); ++__i) \ __FDS_BITS (__arr)[__i] = 0;\ } while (0)#endif /* GNU CC */#define __FD_SET(d, set) \ ((void) (__FDS_BITS (set)[__FD_ELT (d)] |= __FD_MASK (d)))#define __FD_CLR(d, set) \ ((void) (__FDS_BITS (set)[__FD_ELT (d)] &= ~__FD_MASK (d)))#define __FD_ISSET(d, set) \ ((__FDS_BITS (set)[__FD_ELT (d)] & __FD_MASK (d)) != 0) 123456789101112131415161718192021222324252627282930313233343536373839重点看下这两行:
#define __FD_SET(d, set) ((void) (__FDS_BITS (set)[__FD_ELT (d)] |= __FD_MASK (d))) 12__FD_MASK 和 __FD_ELT 宏在上面的代码中已经给出定义:
#define __FD_ELT(d) ((d) / __NFDBITS) #define __FD_MASK(d)((__fd_mask) 1